﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Collections;
using System.IO;
//MicroDesigner
using Jc.MicroDesigner.Objects;

namespace Jc.MicroDesigner.Management
{
    #region Соглашения по элементам
    /*
     * Разрешены следующие типы констукторов для элементов:
     * Base(string name)
     * Circuit(string name), Circuit(ObjectManager manager)
     * Real(ObjectManager manager)
     * Composite(ObjectManager manager, string config)
     */
    #endregion

    /// <summary>
    /// Умный класс для сохранения и загрузки схем
    /// </summary>
    public static class SchemeSerializer
    {
        private static List<Assembly> _Assemblies = new List<Assembly>();
        private static string _AppPath = "";

        /// <summary>
        /// Рабочая папка приложения
        /// </summary>
        public static string AppPath
        {
            get
            {
                return _AppPath;
            }
            set
            {
                _AppPath = value;
            }
        }

        /// <summary>
        /// Сборки приложения, предоставляющие типы элементов
        /// </summary>
        public static List<Assembly> Assemblies
        {
            get
            {
                return _Assemblies;
            }
        }

        #region Additionals Methods

        /// <summary>
        /// Простая функция создания аттрибутов XML для ветви
        /// </summary>
        /// <param name="node">Ветвь</param>
        /// <param name="name">Имя</param>
        /// <param name="value">Значение</param>
        /// <returns>Аттрибут</returns>
        public static void CreateXmlAttr(XmlNode node, string name, string value)
        {
            XmlAttribute attr = node.OwnerDocument.CreateAttribute(name);
            attr.Value = value;
            node.Attributes.Append(attr);
        }

        /// <summary>
        /// Заполнить все свойства из ветви props
        /// Работает для ветвей с тегом "prop", остальные игнорируются
        /// Игнорируемые ветви должен обработать сам элемент в методе LoadAdditionals(...)
        /// </summary>
        /// <param name="obj">Объект</param>
        /// <param name="props">Ветвь свойств</param>
        public static void PostPropsToObject(object obj, XmlNode props)
        {
            Type t = obj.GetType();
            for (int i = 0; i < props.ChildNodes.Count; i++)
            {
                if (props.ChildNodes[i].Name == "prop")
                {
                    XmlNode p = props.ChildNodes[i];
                    PropertyInfo pinf = t.GetProperty(p.Attributes["name"].Value);
                    string prop_value = p.Attributes["value"].Value;
                    object p_val = Convert.ChangeType(prop_value, pinf.PropertyType);
                    pinf.SetValue(obj, p_val, null);
                }
            }
        }

        /// <summary>
        /// Сохраняет список свойств
        /// </summary>
        /// <param name="doc">Xml документ</param>
        /// <param name="parent">Ветвь свойств</param>
        /// <param name="table">Значения</param>
        public static void SaveProps(XmlNode parent, Hashtable table)
        {
            XmlDocument doc = parent.OwnerDocument;
            XmlNode item = null;
            IDictionaryEnumerator enumer = table.GetEnumerator();
            while (enumer.MoveNext())
            {
                item = doc.CreateElement("prop");
                CreateXmlAttr(item, "name", enumer.Key.ToString());
                CreateXmlAttr(item, "value", enumer.Value.ToString());
                parent.AppendChild(item);
            }
        }

        /// <summary>
        /// Получить логический уровень элемента
        /// </summary>
        /// <param name="t">Тип элемента</param>
        /// <returns></returns>
        public static LogicLevel GetElementLevel(Type t)
        {
            LogicLevel result = LogicLevel.Base;
            Type[] types = new Type[] { typeof(CompositeElement), typeof(RealElement), typeof(CircuitElement) };
            LogicLevel[] levels = new LogicLevel[] { LogicLevel.Composite, LogicLevel.Real, LogicLevel.Circuit };
            int i = 0;
            while (
                (i < types.Length) &&
                !((t.IsSubclassOf(types[i])) || (t == types[i]))
                )
                i++;
            if (i < types.Length)
                result = levels[i];
            return result;
        }

        #endregion

        #region Main Methods

        /// <summary>
        /// Сохранить схему
        /// </summary>
        /// <param name="manager">Логическая схема</param>
        /// <returns>Xml Document</returns>
        public static XmlDocument SaveSheme(ObjectManager manager)
        {
            XmlDocument doc = new XmlDocument();
            try
            {
                XmlNode root = doc.CreateElement("root");
                SaveSchemeToNode(manager, root);
                doc.AppendChild(root);
            }
            catch (Exception ex)
            {
                throw new Exception("Ошибка сохранения схемы", ex);
            }
            return doc;
        }

        /// <summary>
        /// Сохранение схемы в элемент XML
        /// Метод используется для сохранения содержимого составных элементов
        /// </summary>
        /// <param name="manager">Схема</param>
        /// <param name="node">Родительский элемент</param>
        public static void SaveSchemeToNode(ObjectManager manager, XmlNode node)
        {
            XmlNode root = node;
            XmlDocument doc = root.OwnerDocument;
            XmlNode item = null; // Активный элемент для записи
            XmlNode propx = null; // Свойство
            XmlNode draw_node = null; //Отрисовка элемента
            Hashtable props = new Hashtable(); // список свойств для сохранения

            #region Document
            XmlNode doc_props = doc.CreateElement("main");
            props.Add("SchemeName", manager.SchemeName);
            props.Add("Description", manager.Description);
            props.Add("Author", manager.Author);
            props.Add("TactCount", manager.TactCount);
            props.Add("TicPerTact", manager.TicPerTact);
            props.Add("InitialSignal", manager.InitialSignal);
            SaveProps(doc_props, props);
            root.AppendChild(doc_props);
            #endregion

            #region Canvas
            XmlNode canvas = doc.CreateElement("canvas"); // Ветвь свойств холста
            props.Clear();
            props.Add("Width", manager.SchemeCanvas.Width);
            props.Add("Height", manager.SchemeCanvas.Height);
            SaveProps(canvas, props);
            root.AppendChild(canvas);
            #endregion

            #region Elements
            XmlNode elements = doc.CreateElement("elements"); // Элементы схемы
            foreach (BaseElement element in manager.Elements)
            {
                #region Attributes

                LogicLevel level = GetElementLevel(element.GetType());
                item = doc.CreateElement("item");
                CreateXmlAttr(item, "type", element.GetType().FullName);
                CreateXmlAttr(item, "name", element.Name);
                CreateXmlAttr(item, "description", element.Description);

                #endregion

                #region Draw Properties
                draw_node = doc.CreateElement("draw");
                props.Clear();
                props.Add("Left", element.DrawOptions.Left);
                props.Add("Top", element.DrawOptions.Top);
                props.Add("Width", element.DrawOptions.Width);
                props.Add("Height", element.DrawOptions.Height);
                SaveProps(draw_node, props);
                item.AppendChild(draw_node);
                #endregion

                #region Specific elements properties
                /* 
                 * Секция дополнительно, сохранять данные можно только в неё
                 * Загрузка будет возможна и из других секций
                 */
                XmlNode additionals = doc.CreateElement("props");
                Dictionary<ConfigSection, XmlNode> nodes = new Dictionary<ConfigSection, XmlNode>();
                nodes.Add(ConfigSection.Additionals, additionals);
                element.SaveAdditionals(nodes);
                item.AppendChild(additionals);
                //всё прочее по уровням
                switch (level)
                {
                    case LogicLevel.Composite:
                        CreateXmlAttr(item, "config", (element as CompositeElement).Config);
                        break;

                    default:
                        break;
                }
                //Если элемент соотвествует уровню элемента цепи
                if ((int)level >= (int)LogicLevel.Circuit)
                {
                    //секция пинов
                    XmlNode pins = doc.CreateElement("pins");
                    CircuitElement ce = element as CircuitElement;
                    foreach (PIN p in ce.PINs)
                    {
                        propx = doc.CreateElement("pin");
                        CreateXmlAttr(propx, "name", p.Name);
                        CreateXmlAttr(propx, "defvalue", p.DefaultSignal.ToString());
                        pins.AppendChild(propx);
                    }
                    item.AppendChild(pins);
                }
                #endregion
                elements.AppendChild(item);
            }
            root.AppendChild(elements);
            #endregion

            #region Connects
            XmlNode connects = doc.CreateElement("connects"); // Соединения
            List<PIN> connected_pins = new List<PIN>();
            foreach (BaseElement el in manager.Elements)
            {
                if (el is CircuitElement)
                {
                    CircuitElement ce = el as CircuitElement;
                    foreach (PIN p in ce.PINs)
                        if (p.FriendPIN != null)
                            connected_pins.Add(p);
                }
            }
            //забавный алгоритм с исключением записанных
            while (connected_pins.Count > 0)
            {
                PIN p = connected_pins[0];
                PIN friend = p.FriendPIN;

                propx = doc.CreateElement("unite");
                CreateXmlAttr(propx, "a", manager.Elements.IndexOf(p.Parent).ToString());
                CreateXmlAttr(propx, "pina", p.Name);
                CreateXmlAttr(propx, "b", manager.Elements.IndexOf(friend.Parent).ToString());
                CreateXmlAttr(propx, "pinb", friend.Name);

                connected_pins.Remove(p);
                connected_pins.Remove(friend);

                connects.AppendChild(propx);
            }
            root.AppendChild(connects);
            #endregion

            #region Watches
            XmlNode watches = doc.CreateElement("watches");
            foreach (SignalContainer sc in manager.Watches)
            {
                XmlNode watch = doc.CreateElement("item");
                CreateXmlAttr(watch, "element", manager.Elements.IndexOf((sc.Source as PIN).Parent).ToString());
                CreateXmlAttr(watch, "pin", sc.Source.Name);
                CreateXmlAttr(watch, "index", sc.SortIndex.ToString());
                CreateXmlAttr(watch, "label", sc.Label);
                watches.AppendChild(watch);
            }
            root.AppendChild(watches);
            #endregion

            //элементы в xml добавляются после изменения всех их дочерних элементов
        }

        /// <summary>
        /// Загрузить схему
        /// </summary>
        /// <param name="doc">Xml документ</param>
        /// <returns>Логическая схема</returns>
        public static ObjectManager LoadSheme(XmlDocument doc)
        {
            ObjectManager scheme = null;
            try
            {
                XmlNode root = null;
                int i = 0;
                while ((i < doc.ChildNodes.Count) && (doc.ChildNodes[i].Name != "root"))
                    i++;
                if (i < doc.ChildNodes.Count)
                    root = doc.ChildNodes[i];
                if (root != null)
                {
                    scheme = LoadSchemeFromNode(root);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Ошибка загрузки схемы:\n\tНеверный формат данных в XML файле", ex);
            }
            return scheme;
        }

        /// <summary>
        /// Загрузка схемы из элемента XML
        /// Метод используется для загрузки содержимого составных элементов
        /// </summary>
        /// <param name="node">Ветвь со схемой</param>
        /// <returns>Схема</returns>
        public static ObjectManager LoadSchemeFromNode(XmlNode node)
        {
            ObjectManager scheme = new ObjectManager();
            scheme.AppPath = AppPath;
            /* 
             * Алгоритм обработки элемента
             * 1. Прочитать тип и вычислить уровень логики
             * 2. Выбрать конструктор
             * 3. Создать объект
             * 4. Записать свойства рисования
             * 5. Присвоить простые свойства из props
             * 6. Вызвать загрузчик свойств элемента
             * 7. Обработать специфичные свойства (pins для Circuit)
             * 8. Добавить элемент в коллекцию схемы
            */
            XmlNode root = node;
            if (root != null)
            {
                //  ./root/doc
                XmlNode doc_node = root.ChildNodes[0];
                PostPropsToObject(scheme, doc_node);

                //  ./root/canvas
                XmlNode canvas = root.ChildNodes[1];
                PostPropsToObject(scheme.SchemeCanvas, canvas);

                // ./root/elements
                XmlNode elements = root.ChildNodes[2];
                #region Разбираем элементы
                foreach (XmlElement el in elements.ChildNodes)
                {
                    string el_type = el.Attributes["type"].Value;
                    string name = el.Attributes["name"].Value;
                    string descr = el.Attributes["description"].Value;

                    Type el_class = null; // тип элемента
                    ConstructorInfo construct = null; // конструктор типа
                    BaseElement obj = null; // объект элемента

                    Dictionary<ConfigSection, XmlNode> nodes = new Dictionary<ConfigSection, XmlNode>();
                    nodes.Add(ConfigSection.Draw, el.ChildNodes[0]);
                    nodes.Add(ConfigSection.Additionals, el.ChildNodes[1]);

                    #region Создание объекта
                    int j = 0;
                    while ((j < _Assemblies.Count) && (_Assemblies[j].GetType(el_type) == null))
                        j++;
                    if (j < _Assemblies.Count)
                        el_class = _Assemblies[j].GetType(el_type);
                    else
                        throw new Exception("Не найден тип элемента схемы");

                    LogicLevel level = GetElementLevel(el_class);
                    if ((int)level >= (int)LogicLevel.Circuit)
                        nodes.Add(ConfigSection.Pins, el.ChildNodes[2]);

                    switch (level)
                    {
                        case LogicLevel.Base:
                            construct = el_class.GetConstructor(new Type[] { typeof(String) });
                            obj = (BaseElement)construct.Invoke(new object[] { name });
                            break;

                        case LogicLevel.Circuit:
                            construct = el_class.GetConstructor(new Type[] { typeof(ObjectManager) });
                            if (construct != null)
                            {
                                obj = (BaseElement)construct.Invoke(new object[] { scheme });
                            }
                            else
                            {
                                construct = el_class.GetConstructor(new Type[] { typeof(String) });
                                obj = (BaseElement)construct.Invoke(new object[] { name });
                            }
                            break;

                        case LogicLevel.Real:
                            construct = el_class.GetConstructor(new Type[] { typeof(ObjectManager) });
                            obj = (BaseElement)construct.Invoke(new object[] { scheme });
                            break;

                        case LogicLevel.Composite:
                            string config = el.Attributes["config"].Value; // файл конфигурации
                            construct = el_class.GetConstructor(new Type[] { typeof(ObjectManager), typeof(String) });
                            obj = (BaseElement)construct.Invoke(new object[] { scheme, config });
                            break;
                    }
                    #endregion

                    #region Установка свойств
                    obj.Name = name;
                    obj.Description = descr;
                    //Грузим стандартные свойства рисования 
                    XmlNode draw = el.ChildNodes[0];
                    PostPropsToObject(obj.DrawOptions, draw);
                    // ... и дополнительные стандартным способом
                    XmlNode additionals = el.ChildNodes[1];
                    PostPropsToObject(obj, additionals);
                    //Даём элементу сдедать приготовления
                    obj.LoadAdditionals(nodes);
                    //Затем грузим пины
                    if ((int)level >= (int)LogicLevel.Circuit)
                    {
                        XmlNode pins = el.ChildNodes[2];
                        CircuitElement ce = obj as CircuitElement;
                        for (j = 0; j < pins.ChildNodes.Count; j++)
                        {
                            ce.PINs[j].Name = pins.ChildNodes[j].Attributes["name"].Value;
                            ce.PINs[j].DefaultSignal = Convert.ToSByte(pins.ChildNodes[j].Attributes["defvalue"].Value);
                        }
                    }
                    #endregion

                    scheme.Elements.Add(obj);
                }
                #endregion

                // ./root/connects
                XmlNode connects = root.ChildNodes[3];
                #region Создаём соединения
                foreach (XmlNode unite in connects.ChildNodes)
                {
                    CircuitElement a = scheme.Elements[Convert.ToInt32(unite.Attributes["a"].Value)] as CircuitElement;
                    CircuitElement b = scheme.Elements[Convert.ToInt32(unite.Attributes["b"].Value)] as CircuitElement;
                    if ((a != null) && (b != null))
                    {
                        PIN pina = a.GetPINByName(unite.Attributes["pina"].Value);
                        PIN pinb = b.GetPINByName(unite.Attributes["pinb"].Value);
                        if ((pina != null) && (pinb != null))
                            PIN.Unite(pina, pinb);
                    }
                }
                #endregion

                // ./root/watches
                XmlNode watches = root.ChildNodes[4];
                #region Добавляем список наблюдений
                foreach (XmlNode wnode in watches.ChildNodes)
                {
                    BaseElement item = scheme.Elements[Convert.ToInt32(wnode.Attributes["element"].Value)];
                    if ((item != null) && (item is CircuitElement))
                    {
                        PIN pin = (item as CircuitElement).GetPINByName(wnode.Attributes["pin"].Value);
                        if (pin != null)
                        {
                            SignalContainer sc = new SignalContainer(pin, 0);
                            sc.Label = wnode.Attributes["label"].Value;
                            sc.SortIndex = Convert.ToUInt16(wnode.Attributes["index"].Value);
                            scheme.Watches.Add(sc);
                        }
                    }
                }
                #endregion
            }
            return scheme;
        }

        /// <summary>
        /// Сохранить сложный элемент
        /// </summary>
        /// <param name="element">Схема элемента</param>
        /// <returns>Xml документ</returns>
        public static XmlDocument SaveCompositeElement(CompositeElement element)
        {
            XmlDocument doc = new XmlDocument();
            try
            {
                XmlNode root = doc.CreateElement("root");
                XmlNode item = doc.CreateElement("item");
                XmlNode additionals = doc.CreateElement("additionals");

                //файл изображения расположен рядом с конфигом
                string img_path = "";
                if (File.Exists(element.DrawOptions.ImageFile))
                    img_path = new FileInfo(element.DrawOptions.ImageFile).Name;

                //0. Аттрибуты
                CreateXmlAttr(item, "name", element.Name);
                CreateXmlAttr(item, "description", element.Description);
                CreateXmlAttr(item, "image", img_path);
                //1. Сгенерировать свойства
                XmlNode properties = doc.CreateElement("properties");
                item.AppendChild(properties);
                //2. Секция пинов
                XmlNode pins = doc.CreateElement("pins");
                foreach (PIN p in element.PINs)
                {
                    XmlNode pin = doc.CreateElement("pin");
                    PIN.PINImage pimg = p.DrawOptions as PIN.PINImage;
                    CreateXmlAttr(pin, "name", p.Name);
                    CreateXmlAttr(pin, "type", p.Type.ToString());
                    CreateXmlAttr(pin, "top", pimg.Top.ToString());
                    CreateXmlAttr(pin, "left", pimg.Left.ToString());
                    CreateXmlAttr(pin, "width", pimg.Width.ToString());
                    CreateXmlAttr(pin, "height", pimg.Height.ToString());
                    CreateXmlAttr(pin, "bindx", pimg.BindingPoint.X.ToString());
                    CreateXmlAttr(pin, "bindy", pimg.BindingPoint.Y.ToString());
                    pins.AppendChild(pin);
                }
                item.AppendChild(pins);
                //4. Внутренняя схема
                XmlNode scheme = doc.CreateElement("scheme");
                SaveSchemeToNode(element.InnerScheme, scheme);
                additionals.AppendChild(scheme);
                //5. Шлюзы
                XmlNode gates = doc.CreateElement("gates");
                foreach (CompositeGate gate in element.Gates)
                {
                    XmlNode gate_node = doc.CreateElement("gate");
                    CreateXmlAttr(gate_node, "interface_pin", gate.InterfacePIN.Name);
                    CreateXmlAttr(gate_node, "element", gate.InnerPIN.Parent.Name);
                    CreateXmlAttr(gate_node, "element_pin", gate.InnerPIN.Name);
                    gates.AppendChild(gate_node);
                }
                additionals.AppendChild(gates);
                //6.
                item.AppendChild(additionals);
                root.AppendChild(item);
                doc.AppendChild(root);
            }
            catch (Exception ex)
            {
                throw new Exception("Ошибка сохранения составного элемента", ex);
            }
            return doc;
        }

        /// <summary>
        /// Загрузить сложный элемент
        /// </summary>
        /// <param name="doc">Xml документ</param>
        /// <returns>Составной элемент</returns>
        public static CompositeElement LoadCompositeElement(string doc)
        {
            CompositeElement element = null;
            try
            {
                ObjectManager manager = new ObjectManager();
                manager.AppPath = AppPath;
                element = new CompositeElement(manager, doc);
            }
            catch (Exception ex)
            {
                throw new Exception("Ошибка загрузки сотавного элемента", ex);
            }
            return element;
        }

        #endregion
    }
}